html
<template>
  <div
    class="chat-content"
    ref="chatContent"
    v-loading="showLoading"
    element-loading-text="消息加载中……"
    element-loading-background="rgba(255, 255, 255, 0.1)"
  >
    <div class="chat-msg-container" ref="container" :style="{ paddingTop: mt, paddingBottom: mb }">
      <chat-msg-item
        v-for="item in showMessageList"
        :key="`${item.session_id}-${item.sender}-${item.timestamp}`"
        :data="item"
      />
    </div>
  </div>
</template>
<script setup lang="ts">
import type { Ref } from "vue";
import {
  chatMessageListFunc, isBottomFunc,
  loadingFunc,
  msgMarginFunc,
  newMessageLengthFunc,
  sessionIdChangedWatcher
} from "@/components/ChatBox/Hooks";
import { sessionIdSelectedFunc } from "@/views/ChatView/Hooks";
import ChatMsgItem from "./ChatMsgItem/index.vue";
import { onMounted, onBeforeUnmount, nextTick } from "vue";
import { emitterEmit, emitterOn, emitterOff } from "@/mitt/mitt";
// 聊天数据
const [getShowMessageList] = chatMessageListFunc();
const showMessageList = getShowMessageList();
// 消息加载loading
const [showLoading, updateLoading] = loadingFunc();
// 获取选中的对话
const [getSessionIdSelected] = sessionIdSelectedFunc();
const sessionIdSelected: Ref<string> = getSessionIdSelected();
// 获取是否在底部
const [getIsBottom, setIsBottom] = isBottomFunc();

// 获取虚拟列表
const [getMargin] = msgMarginFunc();
const [mt, mb] = getMargin();
const chatContent: Ref<Element | null> = ref(null);
const container: Ref<Element | null> = ref(null);

const initVirtualFunc = () => {
  if (chatContent.value) {
    chatContent.value?.removeEventListener("scroll", scroll);
    const timer: number = setInterval(async () => {
      if (chatContent.value && container.value) {
        clearInterval(timer);
        chatContent.value.scrollTop = (container.value as any).offsetHeight;
        await nextTick();
        chatContent.value?.addEventListener("scroll", scroll);
      }
    }, 50);
  }
};
// 监听session_id 变化
sessionIdChangedWatcher(sessionIdSelected, updateLoading, initVirtualFunc);
// 监听内容滚动到底部事件
onMounted(() => {
  emitterOn("scrollEnd", scrollEnd);
});
onBeforeUnmount(() => {
  emitterOff("scrollEnd");
  chatContent.value?.removeEventListener("scroll", scroll);
});
let lastScrollTop = 0;
// 获取容器dom 监听滚动 做虚拟列表
const scroll = (el: any) => {
  if (el.target && sessionIdSelected.value) {
    let scrollTop = el.target.scrollTop;
    if (Math.abs(scrollTop - lastScrollTop) > 20) {
      let offsetY = scrollTop + el.target.offsetHeight - container.value?.offsetHeight;
      Math.abs(offsetY) < 20 ? setIsBottom(true) : setIsBottom(false);
      lastScrollTop = scrollTop;
      emitterEmit(`chatMessage-${sessionIdSelected.value}`, [scrollTop, false, false]);
    }
  }
};
// 滚动到底部
const scrollEnd = () => {
  if (chatContent.value) {
    chatContent.value.scrollTo({ top: (container.value as any).offsetHeight, behavior: "smooth" });
    setIsBottom(true);
  }
};
</script>
<style scoped lang="less">
.chat-content {
  position: relative;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  flex: 1;
  width: 100%;
  height: 100%;
  padding: 0 20px;
}
</style>
